作者:陈广 日期:2019-11-17
此文档描述了高级 NFC 话题,诸如使用各类标签技术、向 NFC 标签写数据、前台调度,这使得一个应用程序即使在其他应用程序筛选相同的 intent 时,也可以在前台处理此 intent。
使用 NFC 标签和 Android 设备时,用于读取和写入标签上的数据的主要格式是 NDEF。当设备扫描到携带 NDEF 数据的标签时,Android 支持解析消息,并在可能的情况下在NdefMessage
中传递消息。但是在某些情况下,当您扫描不包含 NDEF 数据的标签时,或者 NDEF 数据无法映射为 MIME 类型或 URI 时。在这些情况下,您需要直接与标签进行通信,并使用自己的协议(以原始字节为单位)对其进行读取和写入。Android 通过android.nfc.tech
包为这些用例提供了通用支持,如表1所示。可以使用getTechList()
方法来确定所支持的标签技术,并使用由android.nfc.tech
提供的类创建相应的TagTechnology
对象。
表 1:支持的标签技术
类 | 描述 |
---|---|
TagTechnology | 所有标签技术类必须实现的接口 |
NfcA | 提供访问 NFC-A(ISO 14443-3A)的属性和 I/O 操作 |
NfcB | 提供访问 NFC-B(ISO 14443-3B)的属性和 I/O 操作 |
NfcF | 提供访问 NFC-F(JIS 6319-4)的属性和 I/O 操作 |
NfcV | 提供访问 NFC-V(ISO 15693)的属性和 I/O 操作 |
IsoDep | 提供访问 ISO-DEP(ISO 14443-4)的属性和 I/O 操作 |
Ndef | 对于被格式化为 NDEF 的 NFC 标签,提供对 NDEF 数据的访问和操作 |
NdefFormatable | 对可格式化为 NDEF 的标签,提供格式化操作 |
以下标签技术对于 Android 设备来说,不是必须要支持的。
表 2:可选择支持的标签技术
类 | 描述 |
---|---|
MifareClassic | 如果 Android 设备支持 MIFARE,提供访问 MIFARE Classic 的属性和 I/O 操作。 |
MifareUltralight | 如果 Android 设备支持 MIFARE,提供访问 MIFAREUltralight 的属性和I/O操作。 |
当设备扫描到包含 NDEF 数据但不能映射到 MIME 或 URI 的标签时,标签调度系统将尝试使用ACTION_TECH_DISCOVERED
intent 启动 activity。ACTION_TECH_DISCOVERED
还用于非 NDEF 数据的标签。具有此回退功能,可以直接在标签调度系统无法为您分析标签的情况下使用标签上的数据。使用标签技术时的基本步骤如下:
ACTION_TECH_DISCOVERED
intent 筛选指定要处理的标签技术。参考“NFC intent 筛选”获取更多信息。通常情况下,当 NDEF 消息无法映射为 MIME 类型或 URI 时,或扫描到的标签不包含 NDEF 数据时,标签调度系统会启动一个ACTION_TECH_DISCOVERED
intent。获取如何确定这一点的更多信息,请参考“标签调度系统”。Tag
对象:Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
android.nfc.tech
包中某个类的get
工厂方法,获取一个TagTechnology
实例。在调用get
工厂方法之前,可以通过调用getTechList()
来枚举标签所支持的技术。例如,为从Tag
中获取MifareUltralight
实例,可使用如下方法:MifareUltralight.get(intent.getParcelableExtra(NfcAdapter.EXTRA_TAG));
读取和写入 NFC 标签涉及从 intent 获取标标签并打开与标签的通信。必须定义自己的协议栈才能将数据读写到标签中。但是,请记住,在直接使用标签时,仍然可以读取和写入 NDEF 数据。这取决于你想如何安排事情。下面的示例演示如何使用 MIFARE Ultralight 标签。
package com.example.android.nfc;
import android.nfc.Tag;
import android.nfc.tech.MifareUltralight;
import android.util.Log;
import java.io.IOException;
import java.nio.charset.Charset;
public class MifareUltralightTagTester {
private static final String TAG = MifareUltralightTagTester.class.getSimpleName();
public void writeTag(Tag tag, String tagText) {
MifareUltralight ultralight = MifareUltralight.get(tag);
try {
ultralight.connect();
ultralight.writePage(4, "abcd".getBytes(Charset.forName("US-ASCII")));
ultralight.writePage(5, "efgh".getBytes(Charset.forName("US-ASCII")));
ultralight.writePage(6, "ijkl".getBytes(Charset.forName("US-ASCII")));
ultralight.writePage(7, "mnop".getBytes(Charset.forName("US-ASCII")));
} catch (IOException e) {
Log.e(TAG, "IOException while writing MifareUltralight...", e);
} finally {
try {
ultralight.close();
} catch (IOException e) {
Log.e(TAG, "IOException while closing MifareUltralight...", e);
}
}
}
public String readTag(Tag tag) {
MifareUltralight mifare = MifareUltralight.get(tag);
try {
mifare.connect();
byte[] payload = mifare.readPages(4);
return new String(payload, Charset.forName("US-ASCII"));
} catch (IOException e) {
Log.e(TAG, "IOException while reading MifareUltralight message...", e);
} finally {
if (mifare != null) {
try {
mifare.close();
}
catch (IOException e) {
Log.e(TAG, "Error closing tag...", e);
}
}
}
return null;
}
}
前台调度系统允许 activity 拦截 intent 并要求优先于其他 activity 处理相同 intent。使用这个系统需要为 Android 系统构建一些数据结构,以便能够向您的应用程序发送适当的 intent。为启用前台调度系统:
在您的 activity 的onCreate()
方法中添加以下代码:
PendingIntent
对象,以便在标签被扫描时 Android 系统可以向其填充详细信息。PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
TAG_DISCOVERED
intent 的标签进行筛选。下面的代码片段处理NDEF_DISCOVERED
的所有 MIME 类型。你应该只处理你需要的东西。IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try {
ndef.addDataType("*/*"); /* Handles all MIME based dispatches.
You should specify only the ones that you need. */
}
catch (MalformedMimeTypeException e) {
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[] {ndef, };
Object.class.getName()
方法以包含您想要支持的技术的类。techListsArray = new String[][] { new String[] { NfcF.class.getName() } };
重写以下的 activity 生命周期回调,并添加逻辑,以便在 activity 失去焦点(onPause()
)与重新获得焦点(onResume()
)时启用和禁用前景调度。必须从主线程调用enableForegroundDispatch()
,并且只有在 activity 处于前台时才调用(调用onResume()
可以保证这一点)。您还需要执行onNewIntent
回调以处理来自扫描的 NFC 标签的数据。
public void onPause() {
super.onPause();
adapter.disableForegroundDispatch(this);
}
public void onResume() {
super.onResume();
adapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}
public void onNewIntent(Intent intent) {
Tag tagFromIntent = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
//do something with tagFromIntent
}
完整示例,请参考 API Demo 中的 ForegroundDispatch示例。
;